/*
 * Author: nicolas.bredeche(@)lri.fr
 * Created on 16 f�vr. 2006
 * 
 * A neural network is characterized by the possibility to include a bias node (!= NodeNetwork)
 */

package piconode.core.node;

import java.util.ArrayList;

import piconode.core.arc.Arc;
import piconode.core.arc.WeightedArc;
import piconode.ext.ActivationFunction_Linear;
import piconode.toolbox.Tools;
import piconode.visualpiconode.XmlRepresentation;

/**
 * The NeuralNetworkWithBias class provides the minimal basis to define a neural
 * network module. Bias node is connected to all hidden and output nodes. Bias
 * activation function is linear.
 */
public abstract class NeuralNetworkWithBias extends NeuralNetwork implements XmlRepresentation {

	/*
	 * Data
	 */

	protected Neuron _biasNeuron;
	protected boolean _containsBiasNeuron = false;

	protected ArrayList _biasArcList = new ArrayList();

	/*
	 * Constructors
	 */

	/**
	 * create a new network with no bias neuron.
	 */
	public NeuralNetworkWithBias() // by default, network does not contain a
	// biased entry linked to every neurons
	// (except entry node)
	{
		_containsBiasNeuron = false;

	}

	/**
	 * Create a new network that contains or not a bias neuron.
	 * 
	 * @param __activationFunctionFromBiasNeuron
	 */
	public NeuralNetworkWithBias(boolean __bias) {
		_containsBiasNeuron = __bias; // useful for later initialization
	}

	/*
	 * Methods
	 */

	/*
	 * protected void registerNeuron(AtomicNode __neuron) { if ( _init == true ) {
	 * System.err.println("Network::registerNeuron() - cannot handle neuron
	 * registration after initialization"); System.exit(-1); }
	 * 
	 * _allNeuronsUnorderedList.add(__neuron); }
	 */

	/**
	 * Add a bias neuron and connect it to all node in the network
	 * 
	 */
	protected void addBiasNeuron() {
		// create and record the bias neuron
		_biasNeuron = new Neuron(this, new ActivationFunction_Linear(), "bias");
		_biasNeuron.setNetworkInputFlag(true);
		_biasNeuron.setInputValue(1);
		this.registerInputNeuron(_biasNeuron);

		// add arcs from the bias neuron to all neurons in the network (except
		// input nodes)
		for (int i = 0; i != _allNeuronsUnorderedList.size(); i++) {
			if (((Neuron) _allNeuronsUnorderedList.get(i)).isNetworkInput() == false)
				registerBiasArc(new WeightedArc(_biasNeuron, (Neuron) _allNeuronsUnorderedList.get(i), Tools.getArcWeightRandomInitValue()));
		}
	}

	protected void registerBiasArc(WeightedArc __arc) {
		_biasArcList.add(__arc);
	}

	protected WeightedArc getBiasArcAt(int __index) {
		if (this.containsBiasNeuron() != true) {
			System.out.println("Network::getElementInBiasArcList(-) - network contains no bias neuron");
			System.exit(-1);
		}

		if (__index >= getBiasArcListSize() || __index < 0) {
			System.out.println("Network::getElementInBiasArcList(-) - out of range");
			System.exit(-1);
		}

		return ((WeightedArc) _biasArcList.get(__index));
	}

	public boolean containsBiasNeuron() {
		return (_containsBiasNeuron);
	}

	/**
	 * return the total number of arcs including both standard arcs *and* "bias"
	 * arcs for which the source neuron is the bias neuron
	 * 
	 * @return
	 */
	@Override
	public int getNumberOfAllArcs() {
		return (getArcListSize() + getBiasArcListSize());
	}

	/**
	 * return a list with weights values from *all* arcs (i.e. std+bias)
	 * 
	 * @return
	 */
	@Override
	public ArrayList getWeightsFromAllArcs() {
		ArrayList list = new ArrayList();
		list.addAll(this.getWeightsFromStdArcs());
		list.addAll(this.getWeightsFromBiasArcs());
		return (list);
	}

	/**
	 * TODO non-optimal!! should rewrite both setAllArcsWeightValues the other
	 * way round
	 * 
	 * @param __list
	 */
	public void setAllArcsWeightValues(double[] __list) {
		ArrayList list = new ArrayList();
		for (int i = 0; i != __list.length; i++)
			list.add(__list[i]);
		setAllArcsWeightValues(list);
	}

	@Override
	public void setAllArcsWeightValues(ArrayList __list) {
		if (__list.size() != getNumberOfAllArcs()) {
			System.out.println("Network::setAllArcsWeightValues(-) - number of values and weights do not match (waiting for " + getNumberOfAllArcs() + ", " + __list.size() + " given.)");
			System.exit(-1);
		}

		int index = 0;

		// load the weights for the standard arcs

		ArrayList stdWeightList = new ArrayList();

		for (; index != this.getArcListSize(); index++)
			stdWeightList.add(new Double(((Double) __list.get(index)).doubleValue()));

		this.setStdArcsWeightValues(stdWeightList);

		// load the weights for the bias arcs

		ArrayList biasWeightList = new ArrayList();

		for (; index != this.getArcListSize() + this.getBiasArcListSize(); index++)
			biasWeightList.add(new Double(((Double) __list.get(index)).doubleValue()));

		this.setBiasArcsWeightValues(biasWeightList);
	}

	/**
	 * return the number of Arcs *only* from bias neuron (may be useful when
	 * loading/saving weights (e.g. for evolution))
	 * 
	 * @return
	 */
	public int getBiasArcListSize() {
		if (this.containsBiasNeuron() != true) {
			System.out.println("Network::setBiasArcsWeightValues(-) - network contains no bias neuron");
			System.exit(-1);
		}

		return (_biasArcList.size());
	}

	/**
	 * set values for arcs *excepting* arcs that are from the biased neuron -
	 * useful for evolution (update arcs values through evolutionary process)
	 * 
	 * @param __valuesList
	 */

	@Override
	public void setStdArcsWeightValues(ArrayList __valuesList) {
		super.setStdArcsWeightValues(__valuesList);
	}

	/**
	 * return only values for arcs *excepting* those from the biased neuron -
	 * useful for evolution (update arcs values through evolutionary process)
	 * 
	 * @return
	 */
	@Override
	public ArrayList getWeightsFromStdArcs() {
		return super.getWeightsFromStdArcs();
	}

	/**
	 * set values for arcs for which the source *is* the bias neuron (useful for
	 * evolution (update arcs values through evolutionary process))
	 * 
	 * @param __valuesList
	 */
	public void setBiasArcsWeightValues(ArrayList __valuesList) {
		if (this.containsBiasNeuron() != true) {
			System.out.println("Network::setBiasArcsWeightValues(-) - network contains no bias neuron");
			System.exit(-1);
		}

		if (__valuesList.size() != getBiasArcListSize()) {
			System.out.println("Network::setBiasArcsWeightValues(-) - number of values and weights do not match");
			System.exit(-1);
		}

		for (int i = 0; i != __valuesList.size(); i++) {
			((WeightedArc) _biasArcList.get(i)).setWeightValue(((Double) __valuesList.get(i)).doubleValue());
		}
	}

	/**
	 * get values for arcs for which the source is the bias neuron (useful for
	 * evolution (update arcs values through evolutionary process))
	 * 
	 * @param __valuesList
	 */
	public ArrayList getWeightsFromBiasArcs() // useful for evolution (update
	// arcs values through
	// evolutionary process)
	{
		if (this.containsBiasNeuron() != true) {
			System.out.println("Network::setBiasArcsWeightValues(-) - network contains no bias neuron");
			System.exit(-1);
		}

		ArrayList valuesList = new ArrayList();

		for (int i = 0; i != this.getBiasArcListSize(); i++) {
			valuesList.add(new Double(this.getBiasArcAt(i).getWeightValue()));
		}

		return (valuesList);
	}

	/**
	 * enable visualization in piconode editor
	 */

	@Override
	public String toXml() {
		String retour = "<file>\n<nodes>\n";
		if (_nodeList.size() == 0)
			System.out.println("node list is empty");
		for (int i = 0; i < _nodeList.size(); i++)
			retour += ((Neuron) _nodeList.get(i)).toXml() + "\n";
		retour += "</nodes>\n<links>\n";
		for (int i = 0; i < _biasArcList.size(); i++)
			retour += ((Arc) _biasArcList.get(i)).toXml() + "\n";
		for (int i = 0; i < _arcList.size(); i++)
			retour += ((Arc) _arcList.get(i)).toXml() + "\n";
		retour += "</links>\n</file>\n";
		return retour;
	}

}
